package com.hup.utils.jna.hotkey;

import com.sun.jna.platform.KeyboardUtils;
import lombok.extern.log4j.Log4j2;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 简单的实现全局监听快捷键
 * @apiNote
 * 1.注册方法为线程不安全的!
 * 2.如果当前前置的程序,是管理员权限运行的[如任务管理器],则无法监听到按键(如果当前jar也是管理员运行,则可以!);
 * 只支持部分按键: KeyboardUtils.W32KeyboardUtils#toNative(int, int)
 * @author hugan
 * @date 2020/4/8
 */
@Log4j2
public class GlobalHotKeyListener {

    private static class SingletonHolder {
        private static final GlobalHotKeyListener INSTANCE = new GlobalHotKeyListener();
    }

    private static GlobalHotKeyListener getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 从已注册的热键list中检查
     * @param checkIsSameObj true-检查list是否包[入参];false-检查list中的热键是否与[入参]有冲突
     */
    public static boolean contains(HotKey hotKey, boolean checkIsSameObj) {
        if (checkIsSameObj) {
            return getInstance().HOT_KEYS.contains(hotKey);
        } else {
            for (HotKey old : getInstance().HOT_KEYS) {
                if (old.isConflict(hotKey)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * @throws HkConflictException 与旧热键冲突
     */
    public static void add(HotKey hotKey) {
        GlobalHotKeyListener instance = getInstance();
        instance.checkAndAdd(hotKey);
        instance.handleThread();
    }

    /**
     * @throws HkConflictException 与旧热键冲突
     */
    public static void addAll(List<HotKey> hotKeys) {
        GlobalHotKeyListener instance = getInstance();
        for (HotKey hotKey : hotKeys) {
            instance.checkAndAdd(hotKey);
        }
        instance.handleThread();
    }

    public static void remove(HotKey hotKey) {
        GlobalHotKeyListener instance = getInstance();
        if (instance.HOT_KEYS.remove(hotKey)) {
            instance.handleThread();
        }
    }

    public static void removeAll() {
        GlobalHotKeyListener instance = getInstance();
        instance.HOT_KEYS.clear();
        instance.handleThread();
    }

    public static boolean isListening() {
        return getInstance().listening;
    }

    public static ArrayList<HotKey> getAddedHotKey() {
        return new ArrayList<>(getInstance().HOT_KEYS);
    }

    /**
     * 每隔n毫秒轮询一次键盘状态
     */
    private static final int CHECK_KEY_SPAN = 100;

    private final ArrayList<HotKey> HOT_KEYS;
    private final ExecutorService executor;
    private boolean listening;

    private GlobalHotKeyListener() {
        HOT_KEYS = new ArrayList<>();
        executor = Executors.newSingleThreadExecutor();
    }

    private void checkAndAdd(HotKey hotKey) {
        for (HotKey old : HOT_KEYS) {
            if (old.isConflict(hotKey)) {
                throw new HkConflictException(hotKey, old);
            }
        }
        HOT_KEYS.add(hotKey);
    }

    @SuppressWarnings("Duplicates")
    private void handleThread() {
        boolean needListener = !HOT_KEYS.isEmpty();
        log.debug("listening={}, needListener={}", listening, needListener);
        if (listening == needListener) return;
        String tag = "[全局热键监听线程]";
        if (listening) {
            log.debug("正在停止:{}", tag);
            listening = false;
        } else {
            executor.execute(() -> {
                listening = true;
                Thread.currentThread().setName("GlobalHotKey-" + Integer.toHexString(this.hashCode()));
                log.debug("已启动:{}", tag);
                try {
                    while (listening) {
                        checkHotKeys();
                        try {
                            Thread.sleep(CHECK_KEY_SPAN);
                        } catch (InterruptedException e) {
                            log.error("InterruptedException", e);
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                } finally {
                    listening = false;
                    log.debug("已停止:{}", tag);
                }
            });
        }

    }

    /**
     * 判断是否按下已注册的热键,并在[AWT线程]触发热键回调
     */
    private void checkHotKeys() {
        boolean ctrl = KeyboardUtils.isPressed(KeyEvent.VK_CONTROL);
        boolean shift = KeyboardUtils.isPressed(KeyEvent.VK_SHIFT);
        boolean alt = KeyboardUtils.isPressed(KeyEvent.VK_ALT);
        if (!isHotKey(ctrl, shift, alt)) return;
        for (HotKey hotKey : HOT_KEYS) {
            if (ctrl != hotKey.ctrl || shift != hotKey.shift || alt != hotKey.alt) {
                continue;
            }
            if (hotKey.keyCode == null || KeyboardUtils.isPressed(hotKey.keyCode)) {
                if (hotKey.ignoreAction()) {
                    log.debug("忽略事件:{}", hotKey);
                    return;//同一轮询内,已经触发过事件,就退出循环
                }
                log.debug("激活:{}", hotKey);
                hotKey.doActive();
                return;//同一轮询内,已经触发过事件,就退出循环
            }
        }
    }

    /**
     * 判断是否为[组合键]
     *
     * @return 三者必须有两个才是组合键
     */
    private static boolean isHotKey(boolean ctrl, boolean shift, boolean alt) {
        int count = 0;
        if (ctrl) count++;
        if (shift) count++;
        if (alt) count++;
        return count >= 2;
    }

}
